using System;
using System.IO;

namespace Faml.Sources.XDR
{
    public class XDRReader
    {
        protected Stream stream;

        public XDRReader(Stream stream)
        {
            this.stream = stream;
        }

        public object Read(XDRVariable variable)
        {
            /*if (variable.Type == typeof(Int32))
                return ReadInt32(r, variable);
            else*/ if (variable.Type == typeof(Double))
                return ReadFloat64(variable);
           /* else if (variable.Type == typeof(Single))
                ReadReal32(r, variable);
            else if (variable.Type == typeof(Byte))
                ReadInt8(r, variable);*/
            else
                throw new Exception("XDR: variable type is not supported (" + variable.Type.ToString() + ")");

        }

        public void Skip(XDRVariable variable)
        {
            if (variable.Type == typeof(Double))
                SkipFloat64(variable);
        }

        private byte[] ReadBytes(int length)
        {
            byte[] result = new byte[length];
            stream.Read(result, 0, length);
            return result;
        }

        /// <summary>Reads array of ints from BinaryReaders. Performs byte order conversion</summary>
        /// <param name="r">BinaryReader to read from</param>
        /// <param name="dst">Array to write to</param>
        private void ReadArray(int[] dst)
        {
            byte[] src = ReadBytes(dst.Length * 4);
            unsafe
            {
                fixed (int* d = &dst[0])
                fixed (byte* s = &src[0])
                {
                    byte* d2 = (byte*)d;
                    byte* s2 = s;
                    for (int i = 0; i < dst.Length; i++)
                    {
                        d2[3] = s2[0];
                        d2[2] = s2[1];
                        d2[1] = s2[2];
                        d2[0] = s2[3];
                        d2 += 4;
                        s2 += 4;
                    }
                }
            }
        }

        /// <summary>Reads array of doubles from BinaryReaders. Performs byte order conversion</summary>
        /// <param name="r">BinaryReader to read from</param>
        /// <param name="dst">Array to write to</param>
        private void ReadArray(double[] dst)
        {
            byte[] src = ReadBytes(dst.Length * 8);
            unsafe
            {
                fixed (double* d = &dst[0])
                fixed (byte* s = &src[0])
                {
                    byte* d2 = (byte*)d;
                    byte* s2 = s;
                    for (int i = 0; i < dst.Length; i++)
                    {
                        d2[7] = s2[0];
                        d2[6] = s2[1];
                        d2[5] = s2[2];
                        d2[4] = s2[3];
                        d2[3] = s2[4];
                        d2[2] = s2[5];
                        d2[1] = s2[6];
                        d2[0] = s2[7];
                        d2 += 8;
                        s2 += 8;
                    }
                }
            }
        }

        /// <summary>Reads array of floats from BinaryReaders. Performs byte order conversion</summary>
        /// <param name="r">BinaryReader to read from</param>
        /// <param name="dst">Array to write to</param>
        private void ReadArray(float[] dst)
        {
            byte[] src = ReadBytes(dst.Length * 4);
            unsafe
            {
                fixed (float* d = &dst[0])
                fixed (byte* s = &src[0])
                {
                    byte* d2 = (byte*)d;
                    byte* s2 = s;
                    for (int i = 0; i < dst.Length; i++)
                    {
                        d2[3] = s2[0];
                        d2[2] = s2[1];
                        d2[1] = s2[2];
                        d2[0] = s2[3];
                        d2 += 4;
                        s2 += 4;
                    }
                }
            }
        }
/*
        /// <summary>Reads two dimensional array of bytes from binary reader</summary>
        /// <param name="r">BinaryReader to read from</param>
        /// <param name="dst">Array to store to</param>
        private void ReadArray(BinaryReader r, byte[,] dst)
        {
            int dim0 = dst.GetLength(0);
            int dim1 = dst.GetLength(1);
            byte[] buffer = new byte[dim1];
            for (int i = 0; i < dim0; i++)
            {
                r.Read(buffer, 0, dim1);
                for (int j = 0; j < dim1; j++)
                    dst[i, j] = buffer[j];
            }
        }
        */
        private void CheckDimensions(XDRVariable v)
        {
            int[] dim = new int[2];
            ReadArray(dim);
            if (dim[0] != dim[1])
                throw new Exception("Error reading DODS format - dimensions are not duplicated for variable " + v.Name);
            if (dim[0] != v.Length)
                throw new Exception("Error reading DODS format - dimensions do not match for variable " + v.Name);
        }
        /*
        private void ReadInt8Variable(BinaryReader r, ThreddsVariable var)
        {
            CheckDimensions(r, var);
            if (var.Dimensions == 1)
                var.Data = r.ReadBytes(var.Length);
            else if (var.Dimensions == 2)
            {
                byte[,] data = new byte[var.GetMax(0) + 1, var.GetMax(1) + 1];
                ReadArray(r, data);
                var.Data = data;
            }
            else
                throw new Exception("Error readings DODS format - more than two dimensions is not supported");
            int padding = 4 - var.Length % 4;
            if (padding != 4)
                r.ReadBytes(padding);
        }

        private void ReadReal32Variable(BinaryReader r, ThreddsVariable var)
        {
            CheckDimensions(r, var);
            int count = var.GetMax(0) + 1;
            Single[] data = new Single[count];
            ReadArray(r, data);
            var.Data = data;
        }
        */
        public object ReadFloat64(XDRVariable var) 
        {
            CheckDimensions(var);
            if (var.Dimensions == 1)
            {
                int count = var.GetLength(0);
                double[] data = new double[count];
                ReadArray(data);
                return data;
            }
            else if (var.Dimensions == 3)
            {
                double[, ,] data = new double[var.GetLength(0), var.GetLength(1), var.GetLength(2)];
                double[] temp = new double[var.GetLength(2)];
                for (int i = 0; i < var.GetLength(0); i++)
                    for (int j = 0; j < var.GetLength(1); j++)
                    {
                        ReadArray(temp);
                        for (int k = 0; k < var.GetLength(2); k++)
                            data[i, j, k] = temp[k];
                    }
                return data;
            }
            else throw new Exception("This number of dimensions is not supported");

        }

        /// <summary>Reads part of 3d array of doubles from XDR files</summary>
        /// <param name="var">Array variable</param>
        /// <param name="i0">First start index </param>
        /// <param name="j0">Second start index</param>
        /// <param name="k0">Third start index</param>
        /// <param name="width">Width of array to read</param>
        /// <param name="height">Height of array to read</param>
        /// <param name="depth">Depth of array to read</param>
        /// <returns>3d array of width by height by depth elements</returns>
        public object ReadFloat64(XDRVariable var,int i0,int j0,int k0,int width,int height,int depth)
        {
            // TODO: verify bounds i0,j0,k0,width,height,depth
            CheckDimensions(var);
            if (var.Dimensions != 3)
                throw new ArgumentException("Only 3d array variables are supported by this method");
            double[, ,] data = new double[width, height, depth];
            double[] temp = new double[var.GetLength(2)];
            for (int i = 0; i < var.GetLength(0); i++)
                for (int j = 0; j < var.GetLength(1); j++)
                {
                     ReadArray(temp);
                     if(i0 <= i && i < i0 + width &&
                        j0 <= j && j < j0 + height)
                         for (int k = k0; k < k0 + depth; k++)
                             data[i -  i0, j -  j0, k - k0] = temp[k];
                }
            return data;
        }

        private void SkipFloat64(XDRVariable var)
        {
            ReadBytes(8);
            
            if (var.Dimensions == 1)
            {
                ReadBytes(8 * var.GetLength(0));
            }
            else if (var.Dimensions == 3)
            {
                ReadBytes(8 * var.GetLength(0) * var.GetLength(1) * var.GetLength(2));
            }
            else throw new Exception("This number of dimensions is not supported");

        }

        /*
        private void ReadInt32Variable(BinaryReader r, ThreddsVariable var)
        {
            CheckDimensions(r, var);
            int count = var.GetMax(0) + 1;
            Int32[] data = new Int32[count];
            ReadArray(r, data);
            var.Data = data;
        }
        */
    }
}
